/*
 * MemFactory.cpp
 *
 *  Created on: 10.04.2010
 *      Author: artur
 */

#include <kiddie/kiddie.h>
#include <sys/types.h>
#include <sys/mem.h>
#include <sys/errno.h>
#include <platform.h>

#include <Console.h>
#include <hw/Platform.h>
#include <mem/MemFactory.h>

extern kernel::Console cout;
extern kernel::Platform hwplatform;

namespace kernel
{
  namespace mem
  {

    MemFactory::MemFactory()
    {
      OUTDEBUG("[ MemFactory ]");
    }

    MemFactory::~MemFactory()
    {
      OUTDEBUG("[ MemFactory ]");
    }

    void
    MemFactory::init(void)
    {
      OUTDEBUG("[ MemFactory ]");
      this->used_kmem_head.m_length = 0;
      this->used_kmem_head.next = &this->used_kmem_head;
      this->used_kmem_head.prev = &this->used_kmem_head;

      this->free_kmem_head.m_length = 0;
      this->free_kmem_head.next = &this->free_kmem_head;
      this->free_kmem_head.prev = &this->free_kmem_head;
    }

    void *
    MemFactory::alloc(unsigned int size, int type, int order, int align)
    {
      struct m_block *mblk, *tmp;
      vaddr_t addr, mem = 0, end;
      int found = 0;
      size_t f_size;

      ::cout << kernel::Console::DEC << "[ MemFactory ] void *operator "
        "new (size_t size): " << (int) size << " bytes\n";

      if (size <= (PAGE_SIZE - sizeof(struct m_block)))
        {
          mblk = this->free_kmem_head.next;
          while (&this->free_kmem_head != mblk)
            {
              if (size <= mblk->m_length)
                {
                  found = 1;
                  break;
                }
              mblk = mblk->next;
            }

          if (found)
            {
              ::cout << "[ MemFactory ] found memblock\n";

              /* if (length - size) more than size of mem block
               structure, then we cannnot split current block into two mem blocks
               and will use entire block */
              if (size >= (mblk->m_length - sizeof(struct m_block)))
                {
                  /* remove from free blocks list */
                  mblk->prev->next = mblk->next;
                  mblk->next->prev = mblk->prev;
                  /* next add it to used blocks list */
                  mblk->prev = this->used_kmem_head.prev;
                  this->used_kmem_head.prev = mblk;
                  mblk->next = &this->used_kmem_head;
                  mblk->prev->next = mblk;
                  mem = mblk->m_addr;
                }
              else /* split current block onto two blocks */
                {
                  tmp = (struct m_block *) (mblk->m_addr + size);
                  tmp->m_length = mblk->m_length - size
                      - sizeof(struct m_block);
                  tmp->m_addr = (vaddr_t) tmp + sizeof(struct m_block);
                  mblk->m_length = size;

                  /* insert new blocks into lists */
                  tmp->prev = mblk->prev;
                  tmp->next = mblk->next;
                  tmp->prev->next = tmp;
                  tmp->next->prev = tmp;

                  /* add to tail of used blocks list */
                  mblk->prev = this->used_kmem_head.prev;
                  this->used_kmem_head.prev = mblk;
                  mblk->next = &this->used_kmem_head;
                  mblk->prev->next = mblk;
                  mem = mblk->m_addr;
                }
            }
        }

      if (!found)
        {
          // FIXME
          addr = (vaddr_t) pFactory->alloc(size + sizeof(struct m_block), 0);
          ::cout << kernel::Console::HEX
                 << "[ MemFactory ] not found memblock, allocated PAGE=0x"
              << (int) addr << "\n";

          if (NULL == addr)
            {
              EXIT;
              return (void *) 0;
            }

          addr = (vaddr_t) phys_to_virt(addr);

          /* prepare used mem block and add it to used blocks list */
          mblk = (struct m_block *) addr;
          mblk->m_addr = mem = addr + sizeof(struct m_block);
          mblk->m_length = size;
          mblk->m_flags = 1; /* dirty block */
          mblk->m_open_tag = MEM_OPEN_TAG;
          mblk->m_close_tag = MEM_CLOSE_TAG;
          /* add to tail of used blocks list */
          mblk->prev = this->used_kmem_head.prev;
          this->used_kmem_head.prev = mblk;
          mblk->next = &this->used_kmem_head;
          mblk->prev->next = mblk;

          /* prepare free mem block and add it to free blocks list */
          tmp = (struct m_block *) (mblk->m_addr + size);
          end = (vaddr_t) tmp;
          end = (vaddr_t) PAGE_ALIGN(end);
          if (sizeof(struct m_block) < (end - (vaddr_t) tmp))
            {
              tmp->m_length = end - (vaddr_t) tmp
                  - (vaddr_t) sizeof(struct m_block);
              tmp->m_addr = (vaddr_t) tmp + (vaddr_t) sizeof(struct m_block);
              tmp->m_flags = 0; /* free block */
              tmp->m_open_tag = MEM_OPEN_TAG;
              tmp->m_close_tag = MEM_CLOSE_TAG;

              /* add to tail of free blocks list */
              tmp->prev = this->free_kmem_head.prev;
              this->free_kmem_head.prev = tmp;
              tmp->next = &this->free_kmem_head;
              tmp->prev->next = tmp;
            }
          else
            {
              mblk->m_length = end - mblk->m_addr;
            }
        }

      EXIT;

      ::cout << kernel::Console::HEX;
      ::cout << "[ MemFactory ] alloc (size_t size) = " << (int) mem << "\n";
      return (void *) mem;
    }

    MemFactory &
    MemFactory::operator<<(kernel::cmd::CMD cmd)
    {
      OUTDEBUG("[ MemFactory ]");
      switch (cmd)
        {
      case kernel::cmd::INIT:
        init();
        break;
      default:
        break;
        }
      return *this;
    }

    MemFactory &
    MemFactory::operator<<(kernel::mem::page::PageFactory &p)
    {
      OUTDEBUG("[ MemFactory ]");
      pFactory = &p;
      return *this;
    }

  }
}
